package org.yeasy.eval.util;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import org.yeasy.common.constant.CommonConstant;
import org.yeasy.common.util.CommonBeanUtil;
import org.yeasy.eval.annotation.DetailSource;
import org.yeasy.eval.annotation.ValueSource;
import org.yeasy.eval.bean.ValueSourceNode;
import org.yeasy.eval.constant.EvalConstant;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * The type Entity annotation util.实体类注解工具
 *
 * @author yangyishe
 * @date 2022年10月25日 11:22
 */
public class EntityAnnotationUtil {


    /**
     * Calc deep field map map.
     *
     * @param <T>             the type parameter
     * @param <U>             the type parameter
     * @param entityClass     the entity class
     * @param annotationClass the annotation class
     * @return the map
     * @author yangyishe
     * @date 2022年10月25日 14:24
     */
    public static <T, U extends Annotation> Map<String, Field> calc2DeepFieldMap(Class<T> entityClass, Class<U> annotationClass) {
        Map<String, Field> mapValue2Field = calc2SurfaceFieldMap(entityClass, annotationClass);
        List<Field> lstDocDetail = calc2SurfaceFieldList(entityClass, DetailSource.class);
        for (Field detailField : lstDocDetail) {
            ParameterizedType detailInnerType = (ParameterizedType) detailField.getGenericType();
            Class<?> detailInnerClass = (Class<?>) detailInnerType.getActualTypeArguments()[0];
            Map<String, Field> mapInnerValue2Field = calc2SurfaceFieldMap(detailInnerClass, annotationClass);
            for (Map.Entry<String, Field> innerValue2Field : mapInnerValue2Field.entrySet()) {
                String sbNewValue = calc2FieldValue(detailField, DetailSource.class) +
                        EvalConstant.SEPARATOR +
                        innerValue2Field.getKey();

                mapValue2Field.put(sbNewValue, innerValue2Field.getValue());
            }
        }
        return mapValue2Field;
    }

    /**
     * Calc 2 deep field node list.
     *
     * @param <T>         the type parameter
     * @param entityClass the entity class
     * @return the list
     * @author yangyishe
     * @date 2022年10月28日 10:27
     */
    public static <T> List<ValueSourceNode> calc2DeepFieldNode(Class<T> entityClass) {
        Function<Field, ValueSourceNode> convertField2Node = field -> {
            ValueSourceNode mNode = new ValueSourceNode();
            String strValue = calc2FieldValue(field, ValueSource.class);
            ValueSource vs = field.getAnnotation(ValueSource.class);
            mNode.setValue(strValue);
            mNode.setLabel(vs.label());
            mNode.setFullLabel(vs.label());
            mNode.setParentValue("");
            mNode.setFullValue(strValue);
            mNode.setCanRefer(true);
            mNode.setFieldName(field.getName());
            mNode.setFieldType(field.getType().getSimpleName());
            mNode.setFieldClazz(field.getType());
            mNode.setRelation(vs.relation());
            mNode.setKeyType(vs.keyType());
            mNode.setDateType(vs.dateType());
            return mNode;
        };
        return calc2DeepFieldNode(entityClass, convertField2Node);
    }

    /**
     * Calc deep field node list.只接受一层深包
     *
     * @param <T>                    the type parameter
     * @param entityClass            the entity class
     * @param convertValueField2Node the convert value field 2 node
     * @return the list
     * @author yangyishe
     * @date 2022年10月25日 15:01
     */
    public static <T> List<ValueSourceNode> calc2DeepFieldNode(Class<T> entityClass, Function<Field, ValueSourceNode> convertValueField2Node) {
        // 表面ValueSource
        List<Field> lstSurfaceField = calc2SurfaceFieldList(entityClass, ValueSource.class);
        List<ValueSourceNode> lstNode = lstSurfaceField.stream().map(convertValueField2Node).collect(Collectors.toList());

        // detail
        List<Field> lstDetailField = calc2SurfaceFieldList(entityClass, DetailSource.class);
        List<ValueSourceNode> lstDetailNode = lstDetailField.stream().map(o -> {
            ValueSourceNode mNode = new ValueSourceNode();
            String strValue = calc2FieldValue(o, DetailSource.class);
            DetailSource dd = o.getAnnotation(DetailSource.class);
            mNode.setValue(strValue);
            mNode.setLabel(dd.label());
            mNode.setFullLabel(dd.label());
            mNode.setParentValue("");
            mNode.setFullValue(strValue);
            mNode.setCanRefer(false);
            mNode.setFieldName(o.getName());
            mNode.setFieldType(o.getType().getSimpleName());
            mNode.setFieldClazz(o.getType());
            mNode.setRelation("");
            mNode.setKeyType("");
            return mNode;
        }).collect(Collectors.toList());
        lstNode.addAll(lstDetailNode);

        // detail->valueSource
        for (Field detailField : lstDetailField) {
            String parentValue = calc2FieldValue(detailField, DetailSource.class);
            String parentLabel = detailField.getAnnotation(DetailSource.class).label();

            ParameterizedType detailInnerType = (ParameterizedType) detailField.getGenericType();
            Class<?> detailInnerClass = (Class<?>) detailInnerType.getActualTypeArguments()[0];
            List<Field> lstDeepField = calc2SurfaceFieldList(detailInnerClass, ValueSource.class);
            List<ValueSourceNode> lstDeepNode = lstDeepField.stream().map(o -> {
                ValueSourceNode mNode = convertValueField2Node.apply(o);
                mNode.setParentValue(parentValue);
                mNode.setFullValue(parentValue + EvalConstant.SEPARATOR + mNode.getValue());
                mNode.setFullLabel(parentLabel + EvalConstant.SEPARATOR + mNode.getLabel());
                return mNode;
            }).collect(Collectors.toList());
            lstNode.addAll(lstDeepNode);

        }

        return lstNode;
    }

    /**
     * Calc 2 field value string.计算field的value
     *
     * @param <T>             the type parameter
     * @param field           the field
     * @param annotationClass the annotation class
     * @return the string
     * @author yangyishe
     * @date 2022年10月25日 15:01
     */
    public static <T extends Annotation> String calc2FieldValue(Field field, Class<T> annotationClass) {
        T mAnnotation = field.getAnnotation(annotationClass);
        String strValue = ReflectUtil.invoke(mAnnotation, CommonConstant.VALUE);
        if (CharSequenceUtil.isNotEmpty(strValue)) {
            return strValue;
        } else {
            return field.getName();
        }
    }

    /**
     * Calc inner field list list.
     *
     * @param <T>             the type parameter
     * @param <U>             the type parameter
     * @param entityClass     the entity class
     * @param annotationClass the annotation class
     * @return the list
     * @author yangyishe
     * @date 2022年10月25日 11:22
     */
    public static <T, U extends Annotation> List<Field> calc2SurfaceFieldList(Class<T> entityClass, Class<U> annotationClass) {
        return Arrays.stream(entityClass.getDeclaredFields())
                .filter(o -> o.getAnnotation(annotationClass) != null).collect(Collectors.toList());
    }

    /**
     * Calc surface field map map.
     *
     * @param <T>             the type parameter
     * @param <U>             the type parameter
     * @param entityClass     the entity class
     * @param annotationClass the annotation class
     * @return the map
     * @author yangyishe
     * @date 2022年10月25日 14:24
     */
    private static <T, U extends Annotation> Map<String, Field> calc2SurfaceFieldMap(Class<T> entityClass, Class<U> annotationClass) {
        List<Field> lstField = calc2SurfaceFieldList(entityClass, annotationClass);
        Map<String, Field> mapValue2Field = new HashMap<>();
        for (Field surfaceField : lstField) {
            mapValue2Field.put(calc2FieldValue(surfaceField, annotationClass), surfaceField);
        }
        return mapValue2Field;
    }

    /**
     * Calc annotation or field 2 value object.根据注解值或fieldName,计算得出value
     *
     * @param <T>             the type parameter
     * @param <U>             the type parameter
     * @param entity          the entity
     * @param annotationClass the annotation class
     * @param annotationKey   the annotation key
     * @param annotationValue the annotation value
     * @return the object
     * @author yangyishe
     * @date 2022年11月08日 13:59
     */
    public static <T, U extends Annotation> Object calcAnnotationOrFieldName2Value(T entity, Class<U> annotationClass, String annotationKey, String annotationValue) {
        List<Object> lstFieldValue = calcSpecificAnnotation2ValueList(entity, annotationClass, annotationKey, annotationValue);
        if (!lstFieldValue.isEmpty()) {
            return lstFieldValue.get(0);
        } else {
            if (Arrays.stream(entity.getClass().getDeclaredFields()).anyMatch(o -> o.getName().equals(annotationValue))) {
                return CommonBeanUtil.invokeGetter(entity, annotationKey);
            } else {
                return null;
            }
        }
    }

    /**
     * Calc 2 specific annotation value list.获取符合要求注解的字段
     *
     * @param <T>             the type parameter
     * @param <U>             the type parameter
     * @param entity          the entity
     * @param annotationClass the annotation class
     * @param annotationKey   the annotation key
     * @param annotationValue the annotation value
     * @return the list
     * @author yangyishe
     * @date 2022年11月08日 10:25
     */
    public static <T, U extends Annotation> List<Object> calcSpecificAnnotation2ValueList(T entity, Class<U> annotationClass, String annotationKey, Object annotationValue) {
        return Arrays.stream(entity.getClass().getDeclaredFields())
                .filter(o -> o.getAnnotation(annotationClass) != null
                        && annotationValue.equals(ReflectUtil.invoke(o.getAnnotation(annotationClass), annotationKey)))
                .map(o -> CommonBeanUtil.invokeGetter(entity, o.getName()))
                .collect(Collectors.toList());
    }


}
